Skip to content

petri: add NVMe emulator support#3378

Open
babayet2 wants to merge 4 commits intomicrosoft:mainfrom
babayet2:emulated-nvme
Open

petri: add NVMe emulator support#3378
babayet2 wants to merge 4 commits intomicrosoft:mainfrom
babayet2:emulated-nvme

Conversation

@babayet2
Copy link
Copy Markdown
Collaborator

Adds coverage for HyperV NVMe devices leveraging a closed source emulator

This test is currently marked as unstable, the CI runners are not guaranteed to have the NVMe emulator installed

Copilot AI review requested due to automatic review settings April 26, 2026 22:36
@babayet2 babayet2 requested a review from a team as a code owner April 26, 2026 22:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Hyper-V NVMe coverage in the Petri-based vmm_tests suite by provisioning NVMe devices via a closed-source Hyper-V “NVMe emulator” and wiring it through the Hyper-V Petri backend.

Changes:

  • Adds an (unstable) Hyper-V OpenHCL Linux storage test that validates discovery + IO via an NVMe emulator device.
  • Extends the Hyper-V Petri backend to translate VmbusStorageType::Nvme into post-create NVMe emulator device attachment.
  • Adds PowerShell-side plumbing to attach NVMe emulator devices after New-CustomVM completes.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
vmm_tests/vmm_tests/tests/tests/x86_64/storage.rs Adds an unstable Hyper-V NVMe emulator storage test that exercises discovery and IO.
petri/src/vm/hyperv/mod.rs Maps VmbusStorageType::Nvme controllers to NVMe emulator device configs for Hyper-V backend runs.
petri/src/vm/hyperv/powershell.rs Adds nvme_emulator_devices to VM creation args and attaches them post-create via PowerShell.

Comment thread petri/src/vm/hyperv/powershell.rs Outdated
Comment on lines +311 to +315
/// NVMe emulator device configuration (added via AddNvmeEmulator.ps1)
pub struct HyperVNvmeEmulatorDevice {
/// Target VTL (0 or 2)
pub target_vtl: u8,
/// Emulator configuration strings (e.g., `["--vhd path1", "--vhd path2"]`)
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HyperVNvmeEmulatorDevice is described as being added via AddNvmeEmulator.ps1, but the implementation below calls Import-Module HvlDeviceHost; Add-NvmeEmulator .... Please update the doc comment (and the nvme_emulator_devices field comment) to match the actual mechanism so future readers know where this device comes from and what dependency is required.

Suggested change
/// NVMe emulator device configuration (added via AddNvmeEmulator.ps1)
pub struct HyperVNvmeEmulatorDevice {
/// Target VTL (0 or 2)
pub target_vtl: u8,
/// Emulator configuration strings (e.g., `["--vhd path1", "--vhd path2"]`)
/// NVMe emulator device configuration for devices added via the
/// `Add-NvmeEmulator` PowerShell cmdlet from the `HvlDeviceHost` module.
pub struct HyperVNvmeEmulatorDevice {
/// Target VTL (0 or 2)
pub target_vtl: u8,
/// Emulator configuration strings passed to `Add-NvmeEmulator`
/// (for example, `["--vhd path1", "--vhd path2"]`).
///
/// Requires the `HvlDeviceHost` PowerShell module to be available so the
/// `Add-NvmeEmulator` cmdlet can be imported and invoked.

Copilot uses AI. Check for mistakes.
Comment thread petri/src/vm/hyperv/powershell.rs Outdated
Comment on lines +312 to +318
pub struct HyperVNvmeEmulatorDevice {
/// Target VTL (0 or 2)
pub target_vtl: u8,
/// Emulator configuration strings (e.g., `["--vhd path1", "--vhd path2"]`)
pub configuration_strings: Vec<String>,
/// VSID to assign to this device.
pub vsid: Guid,
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HyperVNvmeEmulatorDevice.target_vtl is a u8 with an implicit constraint of "0 or 2". To avoid invalid values flowing through the API, consider modeling this as the existing crate::Vtl/petri::Vtl enum (and convert to the numeric form only at the PowerShell boundary), or introduce a small enum for the NVMe emulator target VTL.

Copilot uses AI. Check for mistakes.
Comment thread petri/src/vm/hyperv/powershell.rs Outdated
// (post-VM-creation). NVMe emulators cannot be added during DefineSystem
// and require a VSID workaround via ModifyResourceSettings.
for device in &args.nvme_emulator_devices {
run_add_nvme_emulator(&vmid, device).await?;
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If run_add_nvme_emulator fails after New-CustomVM succeeds, run_new_customvm will return an error but the VM has already been created. This can leave stray VMs behind (especially in CI where the emulator/module may not be installed). Consider attempting best-effort cleanup on failure (e.g., ensure the VM is off and call Remove-VM) once you have a parsed vmid.

Suggested change
run_add_nvme_emulator(&vmid, device).await?;
if let Err(err) = run_add_nvme_emulator(&vmid, device).await {
let cleanup_err = run_remove_vm(&vmid).await.err();
return match cleanup_err {
Some(cleanup_err) => Err(err).context(format!(
"failed to add NVMe emulator device and failed to remove partially created VM {vmid}: {cleanup_err:#}"
)),
None => Err(err)
.context(format!("failed to add NVMe emulator device; removed partially created VM {vmid}")),
};
}

Copilot uses AI. Check for mistakes.
Comment thread petri/src/vm/hyperv/powershell.rs Outdated
Comment on lines +1277 to +1292
let config_array = device
.configuration_strings
.iter()
.map(|s| format!("'{}'", s.replace('\'', "''")))
.collect::<Vec<_>>()
.join(", ");

let script = format!(
"Import-Module HvlDeviceHost; Add-NvmeEmulator -VmName '{}' -ConfigurationStrings @({}) -TargetVtl {} -Vsid '{{{}}}'",
vmid, config_array, device.target_vtl, device.vsid,
);

run_host_cmd(PowerShellBuilder::new().cmdlet(&script).finish().build())
.await
.map(|_| ())
.context("add_nvme_emulator")
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run_add_nvme_emulator constructs a full PowerShell snippet via format!(...) and passes it as a raw cmdlet string. This bypasses the escaping/quoting guarantees provided by PowerShellBuilder/ps::Value (which this file already uses for arrays/hashtables), making it easier to introduce quoting bugs or injection issues if inputs ever contain unexpected characters. Prefer building the pipeline with PowerShellBuilder (including a ps::Array for ConfigurationStrings) and passing vmid/vsid/target_vtl as typed arguments.

Suggested change
let config_array = device
.configuration_strings
.iter()
.map(|s| format!("'{}'", s.replace('\'', "''")))
.collect::<Vec<_>>()
.join(", ");
let script = format!(
"Import-Module HvlDeviceHost; Add-NvmeEmulator -VmName '{}' -ConfigurationStrings @({}) -TargetVtl {} -Vsid '{{{}}}'",
vmid, config_array, device.target_vtl, device.vsid,
);
run_host_cmd(PowerShellBuilder::new().cmdlet(&script).finish().build())
.await
.map(|_| ())
.context("add_nvme_emulator")
let configuration_strings = ps::Array(
device
.configuration_strings
.iter()
.cloned()
.map(ps::Value::from)
.collect(),
);
run_host_cmd(
PowerShellBuilder::new()
.cmdlet("Import-Module")
.positional("HvlDeviceHost")
.next()
.cmdlet("Add-NvmeEmulator")
.arg("VmName", vmid.to_string())
.arg("ConfigurationStrings", configuration_strings)
.arg("TargetVtl", device.target_vtl)
.arg("Vsid", format!("{{{}}}", device.vsid))
.finish()
.build(),
)
.await
.map(|_| ())
.context("add_nvme_emulator")

Copilot uses AI. Check for mistakes.
Comment thread petri/src/vm/hyperv/mod.rs Outdated
// ordering — hvldevicehost assigns NSIDs 1..N by VHD
// argument order.
let mut sorted_drives: Vec<_> = drives.iter().collect();
sorted_drives.sort_by_key(|(lun, _)| *lun);
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the VmbusStorageType::Nvme branch, the lun/key from drives is only used for sorting, but hvldevicehost assigns NSIDs sequentially (1..N) based on argument order. This means callers that specify explicit NSIDs (e.g., Some(5)) will not get the requested IDs, which can break the VTL2 backing device mapping that expects a specific NSID. Consider validating that the provided keys are exactly 1..N (or otherwise mapping/rewriting) and returning a clear error if the requested NSIDs can't be honored on Hyper-V.

Suggested change
sorted_drives.sort_by_key(|(lun, _)| *lun);
sorted_drives.sort_by_key(|(lun, _)| *lun);
// Hyper-V's NVMe emulator cannot honor arbitrary caller-
// supplied namespace IDs. It assigns NSIDs sequentially
// starting at 1 based on the VHD argument order, so only
// a requested mapping of 1..N can be represented.
for (index, (lun, _)) in sorted_drives.iter().enumerate() {
let expected_nsid = index + 1;
if **lun != expected_nsid {
anyhow::bail!(
"Hyper-V NVMe emulator requires namespace IDs to be exactly 1..N in argument order; requested NSID {} cannot be honored (expected {})",
lun,
expected_nsid
);
}
}

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown

Comment thread petri/src/vm/hyperv/powershell.rs Outdated
// (post-VM-creation). NVMe emulators cannot be added during DefineSystem
// and require a VSID workaround via ModifyResourceSettings.
for device in &args.nvme_emulator_devices {
run_add_nvme_emulator(&vmid, device).await?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please pass in the information needed to create the NVMe emulators as part of the original arguments to New-CustomVM so that we can add the appropriate ResourceSettings to the initial WMI DefineSystem call, rather than invoking a separate powershell session,

Comment thread petri/src/vm/hyperv/powershell.rs Outdated
.join(", ");

let script = format!(
"Import-Module HvlDeviceHost; Add-NvmeEmulator -VmName '{}' -ConfigurationStrings @({}) -TargetVtl {} -Vsid '{{{}}}'",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For things like this that can easy be represented by the builder notation, please use that rather than the "script" backdoor.

Also, this pattern (of using a separate call to modify a nvme drive) should only be used as part of set_vmbus_drive, creation time call should be part of new-customvm for performance reasons.

Comment thread petri/src/vm/hyperv/powershell.rs Outdated
Comment thread petri/src/vm/hyperv/powershell.rs Outdated
Comment thread petri/src/vm/hyperv/powershell.rs Outdated
Comment thread petri/src/vm/hyperv/mod.rs Outdated
Comment thread petri/src/vm/hyperv/mod.rs Outdated
Comment thread petri/src/vm/hyperv/mod.rs Outdated
Comment thread petri/src/vm/hyperv/powershell.rs Outdated
Comment thread petri/src/vm/hyperv/hyperv.psm1 Outdated
Comment thread petri/src/vm/hyperv/mod.rs Outdated
Comment thread petri/src/vm/hyperv/hyperv.psm1
Comment thread petri/src/vm/hyperv/powershell.rs Outdated
Copilot AI review requested due to automatic review settings April 27, 2026 23:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

petri/src/vm/hyperv/mod.rs:389

  • ide_controllers is still computed above, but it is no longer passed into HyperVNewCustomVMArgs (only storage_controllers is set). This will silently drop IDE drive configuration for Hyper-V VMs and also leaves an unused ide_controllers local. Include the ide_controllers field in hyperv_args (or remove the mapping if IDE is intentionally unsupported).
            guest_state_path,
            storage_controllers,
            com_3: supports_com3,
            imc_hiv,
            management_vtl_settings,
            ..HyperVNewCustomVMArgs::from_config(&config, &properties)?
        };

        let vm = HyperVVM::new(hyperv_args, log_source.clone(), driver.clone()).await?;

Comment thread petri/src/vm/hyperv/powershell.rs
Comment thread petri/src/vm/hyperv/powershell.rs Outdated
Comment thread petri/src/vm/hyperv/hyperv.psm1 Outdated
Comment thread petri/src/vm/hyperv/powershell.rs Outdated
Comment thread petri/src/vm/hyperv/mod.rs Outdated
Comment thread petri/src/vm/hyperv/powershell.rs Outdated
Comment thread petri/src/vm/hyperv/mod.rs Outdated
Comment thread petri/src/vm/hyperv/powershell.rs Outdated
@github-actions
Copy link
Copy Markdown

babayet2 and others added 2 commits April 28, 2026 16:17
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 28, 2026 16:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

petri/src/vm/hyperv/mod.rs:365

  • ide_controllers is built from the firmware config above, but it is no longer passed into HyperVNewCustomVMArgs (only storage_controllers is set here, and from_config initializes ide_controllers as empty). This silently drops IDE controller configuration for Hyper-V VMs and also leaves an unused local. Pass the computed ide_controllers into hyperv_args (or remove the mapping if it’s intentionally unsupported).
        let hyperv_args = HyperVNewCustomVMArgs {
            firmware_file: igvm_file.clone(),
            firmware_parameters: openhcl_command_line,
            guest_state_path,
            storage_controllers,
            com_3: supports_com3,
            imc_hiv,
            management_vtl_settings,
            ..HyperVNewCustomVMArgs::from_config(&config, &properties)?
        };

Comment thread petri/src/vm/hyperv/powershell.rs Outdated
Comment thread petri/src/vm/hyperv/powershell.rs
Comment thread petri/src/vm/hyperv/powershell.rs Outdated
assert_eq! panics the test runner instead of returning a structured
error. Use anyhow::ensure! so NSID validation failures surface as
clean error messages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants